package org.bdware.irp.irplib.util;

import org.bdware.irp.irplib.core.*;
import org.bdware.irp.irplib.exception.IrpMessageDecodeException;
import org.bdware.irp.irplib.exception.IrpMessageEncodeException;

import org.apache.log4j.Logger;

public class EncoderUtils {
    public static final int SHORT_SIZE = 2;
    public static final int INT_SIZE = 4;
    public static final int LONG_SIZE = 8;

    private static Logger logger = Logger.getLogger(EncoderUtils.class);

    //write an short value to the object byte buffer
    public static final int writeShortToBuffer(short value, byte[] buf, int offset) {
        buf[offset++] = (byte) ((value & 0xFF00) >> 8);
        buf[offset++] = (byte) (value & 0xFF);
        return SHORT_SIZE;
    }

    //read an short value from the object byte buffer
    public static final short readShortFromBuffer(byte[] buf, int offset) {
        short value = (short) (((buf[offset] & 0xFF)<<8)
                |(buf[offset+1] & 0xFF));
        return value;
    }

    //write an int value to the object byte buffer
    public static final int writeIntToBuffer(int value, byte[] buf, int offset) {
        buf[offset++] = (byte) ((value >> 24) & 0xFF);
        buf[offset++] = (byte) ((value >> 16) & 0xFF);
        buf[offset++] = (byte) ((value >> 8) & 0xFF);
        buf[offset++] = (byte) ((value) & 0xFF);
        return INT_SIZE;
    }

    //read an int value from the object byte buffer
    public static final int readIntFromBuffer(byte[] buf, int offset) {
        int value = (int) (((buf[offset] & 0xFF)<<24)
                |((buf[offset+1] & 0xFF)<<16)
                |((buf[offset+2] & 0xFF)<<8)
                |(buf[offset+3] & 0xFF));
        return value;
    }

    //write an long value to the object byte buffer
    public static final int writeLongToBuffer(long value, byte[] buf, int offset) {
        buf[offset++] = (byte) ((value >> 56) & 0xFF);
        buf[offset++] = (byte) ((value >> 48) & 0xFF);
        buf[offset++] = (byte) ((value >> 40) & 0xFF);
        buf[offset++] = (byte) ((value >> 32) & 0xFF);
        buf[offset++] = (byte) ((value >> 24) & 0xFF);
        buf[offset++] = (byte) ((value >> 16) & 0xFF);
        buf[offset++] = (byte) ((value >> 8) & 0xFF);
        buf[offset++] = (byte) ((value) & 0xFF);
        return LONG_SIZE;
    }

    //read an long value from the object byte buffer
    public static final long readLongFromBuffer(byte[] buf, int offset) {
        long value = (long) (((buf[offset] & 0xFFL)<<56)
                |((buf[offset+1] & 0xFFL)<<48)
                |((buf[offset+2] & 0xFFL)<<40)
                |((buf[offset+3] & 0xFFL)<<32)
                |((buf[offset+4] & 0xFFL)<<24)
                |((buf[offset+5] & 0xFFL)<<16)
                |((buf[offset+6] & 0xFFL)<<8)
                |(buf[offset+7] & 0xFFL));
        return value;
    }

    //write an UTF-8 String to the object byte buffer
    public static final int writeStringByteToBuffer(byte[] value, byte[] buf, int offset) {
        if(value == null)
            return writeIntToBuffer(0, buf, offset);
        else {
            int byteLength = value.length;
            writeIntToBuffer(byteLength, buf, offset);
            System.arraycopy(value, 0, buf, offset + INT_SIZE, byteLength);
            return byteLength + INT_SIZE;
        }
    }

    //read an UTF-8 String from the object byte buffer
    public static final byte[] readStringByteFromBuffer(byte[] buf, int offset) throws IrpMessageDecodeException {
        int byteLength = readIntFromBuffer(buf, offset);
        if(byteLength < 0 || byteLength > IrpCommon.MAX_STRING_ARRAY_SIZE)
            throw new IrpMessageDecodeException("Decode UTF-8 String bytes from the bytesBuffer error!");
        else {
            byte[] stringValue = new byte[byteLength];
            System.arraycopy(buf, offset + INT_SIZE, stringValue, 0, byteLength);
            return stringValue;
        }
    }

    //write UTF-8 String array to the object byte buffer
    public static final int writeStringByteArrayToBuffer(byte[][] values, byte[] buf, int offset) {
        if(values == null)
            return writeIntToBuffer(0, buf, offset);
        else {
            int arrayLength = values.length;
            int offsetLength = writeIntToBuffer(arrayLength, buf, offset);
            for(byte[] value : values){
                offsetLength += writeStringByteToBuffer(value, buf, offset + offsetLength);
            }
            return offsetLength;
        }
    }

    //read UTF-8 String array from the object byte buffer
    public static final byte[][] readStringByteArrayFromBuffer(byte[] buf, int offset) throws IrpMessageDecodeException {
        byte[][] byteArray = null;
        int arrayLength = readIntFromBuffer(buf, offset);
        int offsetPos = offset + INT_SIZE;
        if(arrayLength < 0 || arrayLength > IrpCommon.MAX_ARRAY_LENGTH_SIZE)
            throw new IrpMessageDecodeException("Decode UTF-8 String bytes from the bytesBuffer error!");
        else {
            byteArray = new byte[arrayLength][];
            for(int i = 0; i < arrayLength; i++) {
                byteArray[i] = readStringByteFromBuffer(buf, offsetPos);
                offsetPos += byteArray[i].length + INT_SIZE;
            }
            return byteArray;
        }
    }

    //read int array from the object byte buffer
    public static final int writeIntArrayToBuffer(int[] values, byte[] buf, int offset) {
        if(values == null)
            return writeIntToBuffer(0, buf, offset);
        else {
            int intLength = values.length;
            int offsetLength = writeIntToBuffer(intLength, buf, offset);
            for(int value : values)
            {
                offsetLength += writeIntToBuffer(value, buf, offset + offsetLength);
            }
            return offsetLength;
        }
    }

    //read int array from the object byte buffer
    public static final int[] readIntArrayFromBuffer(byte[] buf, int offset) throws IrpMessageDecodeException {
        int arrayLength = readIntFromBuffer(buf, offset);
        if(arrayLength < 0 || arrayLength > IrpCommon.MAX_ARRAY_LENGTH_SIZE)
            throw new IrpMessageDecodeException("Decode UTF-8 String bytes from the bytesBuffer error!");
        else {
            int[] intArray = new int[arrayLength];
            int offsetPos = offset + INT_SIZE;
            for(int i = 0; i < arrayLength; i++) {
                intArray[i] = readIntFromBuffer(buf, offsetPos);
                offsetPos += INT_SIZE;
            }
            return intArray;
        }
    }

    //encode the message envelope
    public static final byte[] encodeMessageEnvelope(IrpMessageEnvelope envelope) {
        int offsetPos = 0;
        byte[] buf = new byte[IrpCommon.MESSAGE_ENVELOPE_SIZE];
        //encode the majorVersion
        buf[offsetPos++] = envelope.majorVersion;
        //encode the minVersion
        buf[offsetPos++] = envelope.minVersion;
        //encode the messageFlag
        offsetPos += writeShortToBuffer(envelope.getMessageFlagByFlags(), buf, offsetPos);
        //encode the sessionId
        offsetPos += writeIntToBuffer(envelope.sessionId, buf, offsetPos);
        //encode the requestId
        offsetPos += writeIntToBuffer(envelope.requestId, buf, offsetPos);
        //encode the sequenceNumber
        offsetPos += writeIntToBuffer(envelope.sequenceNumber, buf, offsetPos);
        //encode the messageLength
        offsetPos += writeIntToBuffer(envelope.messageLength, buf, offsetPos);
        return buf;
    }

    //decode the message envelope
    public static final void decodeMessageEnvelope(byte[] buf, IrpMessageEnvelope envelope) throws IrpMessageDecodeException {
        int offsetPos = 0;
        if(buf == null || buf.length < IrpCommon.MESSAGE_ENVELOPE_SIZE)
            throw new IrpMessageDecodeException("Decode failed: invalid message envelope!");
        //decode the majorVersion
        envelope.majorVersion = buf[0];
        //decode the minVersion
        envelope.minVersion = buf[1];
        //decode the messageFlag
        envelope.setFlagsByMessageFlag(readShortFromBuffer(buf, 2));
        //decode the sessionId
        envelope.sessionId = readIntFromBuffer(buf, 4);
        //decode the requestId
        envelope.requestId = readIntFromBuffer(buf, 8);
        //decode the sequenceNumber
        envelope.sequenceNumber = readIntFromBuffer(buf, 12);
        //decode the messageLength
        envelope.messageLength = readIntFromBuffer(buf, 16);
        if(envelope.messageLength < 0 || envelope.messageLength > IrpCommon.MAX_MESSAGE_PACKET_LENGTH)
            throw new IrpMessageDecodeException("Decode failed: invalid message body length!");
    }

    //encode the message header
    public static final int encodeMessageHeader(IrpMessageHeader header, byte[] buf, int bodyLen) {
        int offsetPos = 0;
        //encode the opCode
        offsetPos += writeIntToBuffer(header.opCode, buf, offsetPos);
        //encode the responseCode
        offsetPos += writeIntToBuffer(header.responseCode, buf, offsetPos);
        //encode the opFlag
        offsetPos += writeIntToBuffer(header.getOpFlagByFlags(), buf, offsetPos);
        //encode the siteInfoSerialNumber
        offsetPos += writeShortToBuffer(header.siteInfoSerialNumber, buf, offsetPos);
        //encode the recursionCount and reservedSpace
        buf[offsetPos++] = header.recursionCount;
        offsetPos++;
        //encode the expirationTime
        offsetPos += writeIntToBuffer(header.expirationTime, buf, offsetPos);
        //encode the bodyLength
        header.bodyLength = bodyLen;
        offsetPos += writeIntToBuffer(bodyLen, buf, offsetPos);
        return IrpCommon.MESSAGE_HEADER_SIZE;
    }

    //decode the message envelope
    public static final void decodeMessageHeader(byte[] buf, IrpMessageHeader header, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        if(buf == null || buf.length < IrpCommon.MESSAGE_HEADER_SIZE)
            throw new IrpMessageDecodeException("Decode failed: invalid message header!");
        //decode the opCode
        header.opCode = readIntFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE;
        //decode the responseCode
        header.responseCode = readIntFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE;
        //decode the opFlag
        header.setFlagsByOpFlag(readIntFromBuffer(buf, offsetPos));
        offsetPos += INT_SIZE;
        //decode the siteInfoSerialNumber
        header.siteInfoSerialNumber = readShortFromBuffer(buf, offsetPos);
        offsetPos += SHORT_SIZE;
        //decode the recursionCount and reservedSpace
        header.recursionCount = buf[offsetPos++];
        offsetPos++;
        //decode the expirationTime
        header.expirationTime = readIntFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE;
        //decode the bodyLength
        header.bodyLength = readIntFromBuffer(buf, offsetPos);
        if(header.bodyLength < 0)
            throw new IrpMessageDecodeException("Decode failed: invalid message body length!");
    }

    //encode the message credential
    public static final byte[] encodeMessageCredential(IrpMessageCredential credential) {
        credential.signedInfoLength = INT_SIZE + credential.signedInfoDigestAlgorithm.length
                + INT_SIZE + credential.signature.length;
        credential.credentialLength = 1 //space for version
                + 1 //space for reserved
                + SHORT_SIZE //space for options
                + INT_SIZE + credential.signerDoid.length //space for signer doid value
                + INT_SIZE + credential.signedInfoType.length //space for credential type
                + INT_SIZE //space for signedInfoLength
                + INT_SIZE + credential.signedInfoDigestAlgorithm.length //space for digest type
                + INT_SIZE + credential.signature.length; //space for signature

        int offsetPos = 0;
        byte[] credentialByteBuffer = new byte[INT_SIZE + credential.credentialLength];
        //encode the credentialLength
        offsetPos += writeIntToBuffer(credential.credentialLength, credentialByteBuffer, offsetPos);
        //encode the version, reserved, options
        credentialByteBuffer[offsetPos++] = credential.version;
        credentialByteBuffer[offsetPos++] = credential.reserved;
        offsetPos += writeShortToBuffer(credential.options, credentialByteBuffer, offsetPos);
        //encode the signerDoid
        offsetPos += writeStringByteToBuffer(credential.signerDoid, credentialByteBuffer, offsetPos);
        //encode the credential type
        offsetPos += writeStringByteToBuffer(credential.signedInfoType, credentialByteBuffer, offsetPos);
        //encode the signedInfoLength
        offsetPos += writeIntToBuffer(credential.signedInfoLength, credentialByteBuffer, offsetPos);
        //encode the digest type
        offsetPos += writeStringByteToBuffer(credential.signedInfoDigestAlgorithm, credentialByteBuffer, offsetPos);
        //encode the signature
        offsetPos += writeStringByteToBuffer(credential.signature, credentialByteBuffer, offsetPos);

        return credentialByteBuffer;
    }

    //decode the message credential
    public static final void decodeMessageCredential(byte[] buf, int offset, IrpMessageCredential credential) throws IrpMessageDecodeException {
        if(buf == null || buf.length < 0)
            throw new IrpMessageDecodeException("Decode failed: invalid message credential!");
        int offsetPos = offset;
        //decode the credentialLength
        credential.credentialLength = readIntFromBuffer(buf, offsetPos);

        offsetPos += INT_SIZE;
        //decode the version, reserved, options
        credential.version = buf[offsetPos++];
        credential.reserved = buf[offsetPos++];
        credential.options = readShortFromBuffer(buf, offsetPos);
        offsetPos += SHORT_SIZE;
        //decode the signerDoid
        credential.signerDoid = readStringByteFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE + credential.signerDoid.length;
        //decode the credential type
        credential.signedInfoType = readStringByteFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE + credential.signedInfoType.length;
        //decode the signedInfoLength
        credential.signedInfoLength = readIntFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE;
        //decode the digest type
        credential.signedInfoDigestAlgorithm = readStringByteFromBuffer(buf, offsetPos);
        offsetPos += INT_SIZE + credential.signedInfoDigestAlgorithm.length;
        //decode the signature
        credential.signature = readStringByteFromBuffer(buf, offsetPos);
        if(credential.signedInfoLength != (INT_SIZE + credential.signedInfoDigestAlgorithm.length +
                INT_SIZE + credential.signature.length))
            throw new IrpMessageDecodeException("Decode failed: invalid message credential signature length!");
    }

    public static final byte[] encodeMessageBody(IrpMessage msg) throws IrpMessageEncodeException {
        byte bodyBuffer[] = null;
        int opCode = msg.header.opCode;
        int responseCode = msg.header.responseCode;
        switch (responseCode) {
            //request message
            case IrpMessageCode.RC_RESERVED:
                switch (opCode) {
                    case IrpMessageCode.OC_RESOLUTION_DOID:
                        bodyBuffer = encodeResolveDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_RESOLVE_GRS:
                        bodyBuffer = encodeResolveDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_CREATE_ORG_GRS:
                        bodyBuffer = encodeCreateDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_CREATE_DOID:
                        bodyBuffer = encodeCreateDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_BATCH_CREATE_DOID:
                        bodyBuffer = encodeBatchCreateDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_DELETE_DOID:
                        bodyBuffer = encodeDeleteDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_UPDATE_DOID:
                        bodyBuffer = encodeUpdateDoidRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_VERIFY_IRS:
                        bodyBuffer = encodeVerifyIrsRequestBody((IrpRequest)msg);
                        break;
                    case IrpMessageCode.OC_RESOLUTION:
                        bodyBuffer = encodeResolveHandleRequestBody((HandleRequest) msg);
                        break;
                    default:
                        throw new IrpMessageEncodeException("Invalid opCode for the irp request: " + opCode);
                }
                break;
            //response message
            case IrpMessageCode.RC_SUCCESS:
                IrpResponse res = (IrpResponse) msg;
                switch (opCode) {
                    case IrpMessageCode.OC_RESOLUTION_DOID:
                        bodyBuffer = encodeResolveDoidResponseBody(res);
                        break;
                    case IrpMessageCode.OC_CREATE_DOID:
                        bodyBuffer = encodeCreateDoidResponseBody(res);
                        break;
                    case IrpMessageCode.OC_BATCH_CREATE_DOID:
                        bodyBuffer = encodeBatchCreateDoidResponseBody(res);
                        break;
                    case IrpMessageCode.OC_DELETE_DOID:
                        bodyBuffer = encodeDeleteDoidResponseBody(res);
                        break;
                    case IrpMessageCode.OC_UPDATE_DOID:
                        bodyBuffer = encodeUpdateDoidResponseBody(res);
                        break;
                    case IrpMessageCode.OC_VERIFY_IRS:
                        bodyBuffer = encodeVerifyIrsResponseBody(res);
                        break;
                    case IrpMessageCode.OC_CREATE_ORG_GRS:
                        bodyBuffer = encodeCreateDoidResponseBody(res);
                        break;
                    default:
                        throw new IrpMessageEncodeException("Invalid opCode for the irp response: " + opCode);
                }
                break;
            case IrpMessageCode.RC_ERROR:
                bodyBuffer = encodeErrorResponseBody((IrpResponse) msg);
                break;
            default:
                throw new IrpMessageEncodeException("Invalid responseCode for the message: " + responseCode);
        }
        if(bodyBuffer == null) {
            logger.error("Encode message body error [ " + msg.toString() + " ]");
            throw new IrpMessageEncodeException("Encode message body error!");

        }
        return bodyBuffer;
    }

    //decode the message byte buffer
    public static final IrpMessage decodeMessage(byte msgBuf[], int offset, IrpMessageEnvelope envelope) throws IrpMessageDecodeException {
        IrpMessageHeader header = new IrpMessageHeader();
        IrpMessage msg = null;
        decodeMessageHeader(msgBuf, header, offset);
        int opCode = header.opCode;
        int responseCode = header.responseCode;
        int offsetPos = offset + IrpCommon.MESSAGE_HEADER_SIZE;

        switch (responseCode) {
            //request message
            case IrpMessageCode.RC_RESERVED:
                switch (opCode) {
                    case IrpMessageCode.OC_RESOLUTION_DOID:
                        msg = decodeResolveDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_RESOLVE_GRS:
                        msg = decodeResolveDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_CREATE_ORG_GRS:
                        msg = decodeCreateDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_CREATE_DOID:
                        msg = decodeCreateDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_BATCH_CREATE_DOID:
                        msg = decodeBatchCreateDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_DELETE_DOID:
                        msg = decodeDeleteDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_UPDATE_DOID:
                        msg = decodeUpdateDoidRequestBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_VERIFY_IRS:
                        msg = decodeVerifyIrsRequestBody(msgBuf, offsetPos);
                        break;
                    default:
                        throw new IrpMessageDecodeException("Decode failed: Invalid opCode for the irp request: " + opCode);
                }
                break;
            //response message
            case IrpMessageCode.RC_SUCCESS:
                switch (opCode) {
                    case IrpMessageCode.OC_RESOLUTION_DOID:
                        msg = decodeResolveDoidResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_CREATE_DOID:
                        msg = decodeCreateDoidResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_BATCH_CREATE_DOID:
                        msg = decodeBatchCreateDoidResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_DELETE_DOID:
                        msg = decodeDeleteDoidResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_UPDATE_DOID:
                        msg = decodeUpdateDoidResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_VERIFY_IRS:
                        msg = decodeVerifyIrsResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_RESOLUTION:
                        msg = decodeResolveHandleResponseBody(msgBuf, offsetPos);
                        break;
                    case IrpMessageCode.OC_CREATE_ORG_GRS:
                        msg = decodeCreateDoidResponseBody(msgBuf, offsetPos);
                        break;
                    default:
                        throw new IrpMessageDecodeException("Decode failed: Invalid opCode for the irp response: " + opCode);
                }
                break;
            case IrpMessageCode.RC_ERROR:
                msg = decodeErrorResponseBody(opCode, responseCode, msgBuf, offsetPos);
                break;
            default:
                throw new IrpMessageDecodeException("Invalid responseCode for the message: " + responseCode);
        }

        if(msg == null) {
            logger.error("Decode message body error [ " + msg.toString() + " ]");
            throw new IrpMessageDecodeException("Decode message body error!");
        }

        msg.encodedMessageBody = new byte[IrpCommon.MESSAGE_HEADER_SIZE + header.bodyLength];
        System.arraycopy(msgBuf, offset, msg.encodedMessageBody, 0, IrpCommon.MESSAGE_HEADER_SIZE + header.bodyLength);
        msg.envelope = envelope;
        msg.header = header;
        offsetPos += header.bodyLength;

        // read the signature, if there is room for one
        if (offset + envelope.messageLength >= offsetPos + INT_SIZE) {
            int creLength = readIntFromBuffer(msgBuf, offsetPos);
            if(creLength < 0 || creLength != (offset + envelope.messageLength - offsetPos - INT_SIZE)) {
                throw new IrpMessageDecodeException("Decode failed: invalid message credential!");
            } else if (creLength == 0){
                msg.credential.credentialLength = 0;
                msg.encodedCredential = null;
            } else{
                decodeMessageCredential(msgBuf, offsetPos, msg.credential);
                msg.encodedCredential = new byte[msg.credential.credentialLength];
                System.arraycopy(msgBuf, offsetPos, msg.encodedCredential, 0, msg.credential.credentialLength);
            }
        }

        return msg;
    }

    //resolve operation
    public static final byte[] encodeResolveDoidRequestBody(IrpRequest req) {
        int resolveKeysLength = 0;
        if(req.requestedKeys != null) {
            for(byte[] requestedKey : req.requestedKeys) {
                //the utf-8 bytes length and the number of octets of the character string(int)
                resolveKeysLength += (INT_SIZE + requestedKey.length);
            }
        }
        //space for doid and resolveKeys
        int bodyLength = INT_SIZE + req.doid.length + INT_SIZE + resolveKeysLength;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the doid
        offsetPos += writeStringByteToBuffer(req.doid, bodyBuffer, offsetPos);
        //encode the keys array
        offsetPos += writeStringByteArrayToBuffer(req.requestedKeys, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpRequest decodeResolveDoidRequestBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + doid.length;
        //decode the keys array
        byte[][] requestedKeys = readStringByteArrayFromBuffer(msgBuf, offsetPos);

        return IrpRequest.newIrsResolveRequest(doid, requestedKeys);
    }

    public static final byte[] encodeResolveDoidResponseBody(IrpResponse res) {
        int bodyLength = INT_SIZE + res.doid.length + // space for the doid
                INT_SIZE + // space for value list length
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        // add the size for each return values
        if(res.doidValues != null){
            for (byte[] value : res.doidValues) {
                bodyLength += (INT_SIZE + value.length);
            }
        }
        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the doid
        offsetPos += writeStringByteToBuffer(res.doid, bodyBuffer, offsetPos);
        // encode the values...
        if(res.doidValues != null) {
            writeStringByteArrayToBuffer(res.doidValues, bodyBuffer, offsetPos);
        }else {
            writeIntToBuffer(0, bodyBuffer, offsetPos);
        }

        return bodyBuffer;
    }

    public static final IrpResponse decodeResolveDoidResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        int offsetPos = offset;
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + doid.length;
        //decode the keys array
        int valuesLength = readIntFromBuffer(msgBuf, offsetPos);
        byte[][] requestedValues = null;
        if(valuesLength != 0){
            requestedValues = readStringByteArrayFromBuffer(msgBuf, offsetPos);
        }
        return IrpResponse.newIrsResolveResponse(doid, requestedValues);
    }


    //create operation
    public static final byte[] encodeCreateDoidRequestBody(IrpRequest req) {
        int valuesLength = 0;
        if(req.doidValues != null) {
            for(byte[] doidValue : req.doidValues) {
                //the utf-8 bytes length and the number of octets of the character string(int)
                valuesLength += (INT_SIZE + doidValue.length);
            }
        }
        //space for doid and doidvalues
        int bodyLength = INT_SIZE + req.doid.length + INT_SIZE + valuesLength;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the doid
        offsetPos += writeStringByteToBuffer(req.doid, bodyBuffer, offsetPos);
        //encode the doid values array
        offsetPos += writeStringByteArrayToBuffer(req.doidValues, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpRequest decodeCreateDoidRequestBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + doid.length;
        //decode the doid values array
        byte[][] doidValues = readStringByteArrayFromBuffer(msgBuf, offsetPos);

        return IrpRequest.newIrsCreateDoidRequest(doid, doidValues);
    }

    public static final byte[] encodeCreateDoidResponseBody(IrpResponse res) {
        int bodyLength = INT_SIZE + res.doid.length + // space for the doid
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the doid
        offsetPos += writeStringByteToBuffer(res.doid, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpResponse decodeCreateDoidResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offset);
        return IrpResponse.newIrsCreateDoidResponse(doid);
    }

    //batch create operation
    public static final byte[] encodeBatchCreateDoidRequestBody(IrpRequest req) {
        int valuesLength = 0;
        if(req.doidValues != null) {
            for(byte[] doidValue : req.doidValues) {
                //the utf-8 bytes length and the number of octets of the character string(int)
                valuesLength += (INT_SIZE + doidValue.length);
            }
        }
        //space for count and doidvalues
        int bodyLength = INT_SIZE + INT_SIZE + valuesLength;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the count
        offsetPos += writeIntToBuffer(req.createNumber, bodyBuffer, offsetPos);
        //encode the doid values array
        offsetPos += writeStringByteArrayToBuffer(req.doidValues, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpRequest decodeBatchCreateDoidRequestBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        //decode the count
        int count = readIntFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE;
        //decode the doid values array
        byte[][] doidValues = readStringByteArrayFromBuffer(msgBuf, offsetPos);

        return IrpRequest.newIrsBatchCreateDoidRequest(count, doidValues);
    }

    public static final byte[] encodeBatchCreateDoidResponseBody(IrpResponse res) {
        int doidsLength = 0;
        if(res.doids!= null) {
            for(byte[] doidEl : res.doids) {
                //the utf-8 bytes length and the number of octets of the character string(int)
                doidsLength += (INT_SIZE + doidEl.length);
            }
        }
        int bodyLength = INT_SIZE + doidsLength + // space for the doids
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the doids
        offsetPos += writeStringByteArrayToBuffer(res.doids, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpResponse decodeBatchCreateDoidResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        //decode the doids
        byte[][] doids = readStringByteArrayFromBuffer(msgBuf, offset);
        return IrpResponse.newIrsBatchCreateDoidResponse(doids);
    }


    //update operation
    public static final byte[] encodeUpdateDoidRequestBody(IrpRequest req) {
        int valuesLength = 0;
        if(req.doidValues != null) {
            for(byte[] doidValue : req.doidValues) {
                //the utf-8 bytes length and the number of octets of the character string(int)
                valuesLength += (INT_SIZE + doidValue.length);
            }
        }
        //space for doid and doidvalues
        int bodyLength = INT_SIZE + req.doid.length + INT_SIZE + valuesLength;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the doid
        offsetPos += writeStringByteToBuffer(req.doid, bodyBuffer, offsetPos);
        //encode the doid values array
        offsetPos += writeStringByteArrayToBuffer(req.doidValues, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpRequest decodeUpdateDoidRequestBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + doid.length;
        //decode the handle values array
        byte[][] doidValues = readStringByteArrayFromBuffer(msgBuf, offsetPos);

        return IrpRequest.newIrsUpdateDoidRequest(doid, doidValues);
    }

    public static final byte[] encodeUpdateDoidResponseBody(IrpResponse res) {
        int bodyLength = INT_SIZE + res.doid.length + // space for the doid
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the doid
        offsetPos += writeStringByteToBuffer(res.doid, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpResponse decodeUpdateDoidResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offset);
        return IrpResponse.newIrsUpdateDoidResponse(doid);
    }


    //delete operation
    public static final byte[] encodeDeleteDoidRequestBody(IrpRequest req) {
        //space for doid
        int bodyLength = INT_SIZE + req.doid.length;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the doid
        offsetPos += writeStringByteToBuffer(req.doid, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpRequest decodeDeleteDoidRequestBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offset);
        return IrpRequest.newIrsDeleteDoidRequest(doid);
    }

    public static final byte[] encodeDeleteDoidResponseBody(IrpResponse res) {
        int bodyLength = INT_SIZE + res.doid.length + // space for the doid
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the doid
        offsetPos += writeStringByteToBuffer(res.doid, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpResponse decodeDeleteDoidResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offset);
        return IrpResponse.newIrsDeleteDoidResponse(doid);
    }

    //error response
    public static final byte[] encodeErrorResponseBody(IrpResponse res) {
        int bodyLength = INT_SIZE + res.responseMessage.length + // space for the error message
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the responseMessage
        writeStringByteToBuffer(res.responseMessage, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpResponse decodeErrorResponseBody(int opCode, int responseCode, byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        //decode the responseMessage
        byte[] responseMessage = readStringByteFromBuffer(msgBuf, offset);
        return IrpResponse.newErrorResponse(opCode, responseCode, responseMessage);
    }

    //verity operation
    public static final byte[] encodeVerifyIrsRequestBody(IrpRequest req) {
        //space for doid and address
        int bodyLength = INT_SIZE + req.doid.length + INT_SIZE + req.address.length;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the doid
        offsetPos += writeStringByteToBuffer(req.doid, bodyBuffer, offsetPos);
        //encode the address
        offsetPos += writeStringByteToBuffer(req.address, bodyBuffer, offsetPos);

        return bodyBuffer;
    }

    public static final IrpRequest decodeVerifyIrsRequestBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + doid.length;
        //decode the address
        byte[] address = readStringByteFromBuffer(msgBuf, offsetPos);

        return IrpRequest.newVerityIrsServerRequest(doid, address);
    }

    public static final byte[] encodeVerifyIrsResponseBody(IrpResponse res) {
        int bodyLength = INT_SIZE + res.doid.length + // space for the doid
                INT_SIZE + res.responseMessage.length + // space for the verify result
                (res.requestDigestNeeded ? 1 + res.requestDigest.length : 0); // request digest

        byte bodyBuffer[] = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(res.header, bodyBuffer, bodyLength);

        if (res.requestDigestNeeded) {
            //to do
        }
        //encode the doid
        offsetPos += writeStringByteToBuffer(res.doid, bodyBuffer, offsetPos);
        //encode the verify result
        offsetPos += writeStringByteToBuffer(res.responseMessage, bodyBuffer, offsetPos);
        return bodyBuffer;
    }

    public static final IrpResponse decodeVerifyIrsResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException{
        int offsetPos = offset;
        //decode the doid
        byte[] doid = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + doid.length;
        byte[] result = readStringByteFromBuffer(msgBuf, offsetPos);
        return IrpResponse.newVerifyIrsResponse(doid, result);
    }

    public static final byte[] encodeResolveHandleRequestBody(HandleRequest req) {
        int resolveTypesLength = 0;
        if(req.handleTypes != null) {
            for(byte[] requestedType : req.handleTypes) {
                //the utf-8 bytes length and the number of octets of the character string(int)
                resolveTypesLength += (INT_SIZE + requestedType.length);
            }
        }
        //space for handle, resolveIndexs and resolveTypes
        int bodyLength = INT_SIZE + req.handle.length +
                INT_SIZE + (req.handleIndexes == null ? 0 : req.handleIndexes.length) * INT_SIZE +
                INT_SIZE + resolveTypesLength;
        byte[] bodyBuffer = new byte[bodyLength + IrpCommon.MESSAGE_HEADER_SIZE];
        int offsetPos = 0;
        //encode the message header
        offsetPos += encodeMessageHeader(req.header, bodyBuffer, bodyLength);
        //encode the handle
        offsetPos += writeStringByteToBuffer(req.handle, bodyBuffer, offsetPos);
        //encode the indexs array
        offsetPos += writeIntArrayToBuffer(req.handleIndexes, bodyBuffer, offsetPos);
        //encode the types array
        offsetPos += writeStringByteArrayToBuffer(req.handleTypes, bodyBuffer, offsetPos);
        return bodyBuffer;
    }
    public static final HandleResponse decodeResolveHandleResponseBody(byte[] msgBuf, int offset) throws IrpMessageDecodeException {
        int offsetPos = offset;
        //decode the doid
        byte[] handle = readStringByteFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE + handle.length;
        //decode the keys array
        int valuesLength = readIntFromBuffer(msgBuf, offsetPos);
        offsetPos += INT_SIZE;
        byte[][] handleValues = null;
        if(valuesLength != 0){
            handleValues = new byte[valuesLength][];
            for (int i = 0; i < valuesLength; i++) {
                int valLen = calculateHandleValueSize(msgBuf, offsetPos);
                handleValues[i] = new byte[valLen];
                System.arraycopy(msgBuf, offsetPos, handleValues[i], 0, valLen);
                offsetPos += valLen;
            }
        }
        return HandleResponse.newResolveHandleResponse(handle, handleValues);
    }

    public static final int calculateHandleValueSize(byte[] msgBuf, int offset){
        int origOffset = offset;
        offset += INT_SIZE + // index - 4 bytes
                INT_SIZE + // timestamp - 4 bytes
                1 + // ttlType - 1 byte
                INT_SIZE + // ttl - 4 bytes
                1; // permissions - 1 byte
        // type field
        int fieldLen = readIntFromBuffer(msgBuf, offset);
        offset += INT_SIZE + fieldLen;
        // data field
        fieldLen = readIntFromBuffer(msgBuf, offset);
        offset += INT_SIZE + fieldLen;
        // references (number of)
        fieldLen = readIntFromBuffer(msgBuf, offset);
        offset += INT_SIZE;
        // each reference - hdl length + hdl + index
        for (int i = 0; i < fieldLen; i++) {
            int refLen = readIntFromBuffer(msgBuf, offset);
            offset += INT_SIZE + refLen + INT_SIZE;
        }
        return offset - origOffset;
    }

}
